package edu.calvin.djs32.cs214.interpreters.calculator;

import edu.calvin.cs214.hobbes2008.ApplicationTIR;
import edu.calvin.cs214.hobbes2008.HobbesTIR;
import edu.calvin.cs214.hobbes2008.HobbesTIRVisitor;
import edu.calvin.cs214.hobbes2008.IdentifierTIR;
import edu.calvin.cs214.hobbes2008.IfTIR;
import edu.calvin.cs214.hobbes2008.IntegerTIR;
import edu.calvin.cs214.hobbes2008.LetFunctionTIR;
import edu.calvin.cs214.hobbes2008.LetTIR;
import edu.calvin.cs214.hobbes2008.StringTIR;
import edu.calvin.cs214.hobbes2008.TupleTIR;
import edu.calvin.cs214.hobbes2008.environments.IEnvironment;

public class Calculator implements HobbesTIRVisitor<HobbesTIR>, ICalculator{
	
	public IEnvironment<Applicable> myApplicables;
	public IEnvironment<HobbesTIR> myEnvironment;

	public Calculator(IEnvironment<Applicable> prims, IEnvironment<HobbesTIR> environment) {
		myApplicables = prims;
		myEnvironment = environment;
		
	}

	public HobbesTIR visitApplication(ApplicationTIR application) {

			HobbesTIR[] arguments = new HobbesTIR[application.getTuple().getLength()];
			for (int i = 0; i < application.getTuple().getLength(); i++){
				arguments[i] = application.getTuple().getElement(i).accept(this);
			}
			Applicable primitiveRet = myApplicables.get(application.getIdentifier());
			return primitiveRet.apply(this, arguments);
		
	}

	public HobbesTIR visitInteger(IntegerTIR integer) {
		return integer;
	}

	public HobbesTIR visitString(StringTIR string) {
		return string;
	}

	public HobbesTIR visitTuple(TupleTIR tuple) {
		HobbesTIR[] arguments = new HobbesTIR[tuple.getLength()];
		for (int i = 0; i < tuple.getLength(); i++){
			arguments[i] = tuple.getElement(i).accept(this);
		}
		return new TupleTIR(arguments);
	}

	public HobbesTIR visitIf(IfTIR ifExpression) {
		if (ifExpression.getCondition().accept(this).equals(new TupleTIR())){
			return ifExpression.getElse().accept(this);
		}
		return ifExpression.getThen().accept(this);
	}

	public HobbesTIR visitIdentifier(IdentifierTIR identifier) {
		return myEnvironment.get(identifier);
	}

	public HobbesTIR visitLet(LetTIR letTIR) {
		IdentifierTIR ident = letTIR.getIdentifier();
		HobbesTIR init = letTIR.getInitialization().accept(this);
		IEnvironment<HobbesTIR> nested = myEnvironment.create();
		nested.add(ident, init);
		HobbesTIR body = letTIR.getBody();
		Calculator nestedCalculator = new Calculator(this.getPrimitives(), nested);
		return body.accept(nestedCalculator);
	}

	public HobbesTIR visitLetFunction(LetFunctionTIR letFunc) {
		//Identifier that contains closure is the letfunc identifier
		IEnvironment<Applicable> nextApplicables = myApplicables.create();
		if (letFunc.getFunctionName().toString().startsWith("closure_")){
			return null;
		}else{
			nextApplicables.add(letFunc.getFunctionName(), new ApplicableLambda(letFunc.getFunctionLambda()));
			Calculator nestedCalculator = new Calculator(nextApplicables, myEnvironment);
			return letFunc.getBody().accept(nestedCalculator);
		}
		
	}

	public IEnvironment<Applicable> getPrimitives() {
		return myApplicables;
	}
	
	public IEnvironment<HobbesTIR> getVariables(){
		return myEnvironment;
	}
	
	@Override
	public boolean equals(Object o) {
	    return (o != null) && getClass().equals(o.getClass())
	      && equals((Calculator) o);
	}
	private boolean equals(Calculator other) {
	    return myApplicables.equals(other.myApplicables) && myEnvironment.equals(other.myEnvironment);
	}
	@Override
	public int hashCode() {
	    return myEnvironment.hashCode();
	}

	public ICalculator create(IEnvironment<Applicable> applicables,
			IEnvironment<HobbesTIR> environment) {
		return new Calculator(applicables, environment);
	}

}
